// log.hpp(日志封装)

/* 文件描述
Log log = Log(bool debugShow = true,    // 选择是否显示 DEBUG 等级的日志消息
    std::string writeMode = "SCREEN",   // 选择日志的打印方式
    std::string logFileName = "log"     // 选择日志的文件名称
);
log.WriteModeEnable();      // 中途可以修改日志的打印方式
log.LogMessage(DEBUG, "%s %d", __FILE__, __LINE__));     // 打印日志
log.LogMessage(NORMAL, "%s %d", __FILE__, __LINE__));     // 打印日志
log.LogMessage(WARNING, "%s %d", __FILE__, __LINE__));     // 打印日志
log.LogMessage(ERROR, "%s %d", __FILE__, __LINE__));     // 打印日志
log.LogMessage(FATAL, "%s %d", __FILE__, __LINE__));     // 打印日志
*/

#pragma once
#include <iostream>
#include <string>
#include <fstream>
#include <cstdio>
#include <cstdarg>
#include <ctime>
#include <pthread.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>

namespace limou
{
    // 日志级别
    #define DEBUG 0 // 调试
    #define NORMAL 1 // 正常（或者叫 INFO）
    #define WARNING 2 // 警告
    #define ERROR 3 // 错误
    #define FATAL 4 // 致命

    enum WriteMode {
        SCREEN = 5,
        ONE_FILE,
        CLASS_FILE
    };

    const char* gLevelMap[] = {
        "DEBUG", // debug 模式
        "NORMAL", // 正常（或者叫 INFO）
        "WARNING", // 警告
        "ERROR", // 非致命错误
        "FATAL" // 严重错误
    };

    const std::string logdir = "log_dir";

    // 本日志功能主要有: 日志等级、发送时间、日志内容
    class Log
    {
        // TODO: 可以尝试做一下显示代码行数、调用所处文件、运行用户
    private:
        void __WriteLogToOneFile(std::string logFileName, const std::string& message)
        {
            std::ofstream out(logFileName, std::ios::app);
            if (!out.is_open())
                return;
            out << message;
            out.close();
        }

        void __WriteLogToClassFile(const int& level, const std::string& message)
        {
            std::string logFileName = "./";
            logFileName += logdir;
            logFileName += "/";
            logFileName += _logFileName;
            logFileName += "_";
            logFileName += gLevelMap[level];

            __WriteLogToOneFile(logFileName, message);
        }

        void _WriteLog(const int& level, const std::string& message)
        {
            switch (_writeMode)
            {
            case SCREEN: // 向屏幕输出
                std::cout << message;
                break;
            case ONE_FILE: // 向单个日志文件输出
                __WriteLogToOneFile("./" + logdir + "/" + _logFileName, message);
                break;
            case CLASS_FILE: // 向多个日志文件输出
                __WriteLogToClassFile(level, message);
                break;
            default:
                std::cout << "write mode error!!!" << std::endl;
                break;
            }
        }

    public:
        // 构造函数, debugShow 为是否显示 debug 消息, writeMode 为日志打印模式, logFileName 为日志文件名
        Log(bool debugShow = true, const WriteMode& writeMode = SCREEN, std::string logFileName = "log")
            : _debugShow(debugShow), _writeMode(writeMode), _logFileName(logFileName)
        {
            mkdir(logdir.c_str(), 0775); // 创建目录
        }

        // 通过手动设置 mode = [SCREEN | ONE_FILE | CLASS_FILE] 来修改日志的输出模式
        void WriteModeEnable(const WriteMode& mode)
        {
            _writeMode = mode;
        }

        // 设置日志等级 level = [DEBUG | NORMAL | WARNING | ERROR | FATAL], 并且拼接“标准日志+用户自定义日志”的日志消息, 然后根据工作模式来输出
        void LogMessage(const int& level, const char* format, ...)
        {
            // 1.若不是 debug 模式，且 level == DEBUG 则不做任何事情
            if (_debugShow == false && level == DEBUG)
                return;

            // 2.收集日志标准部分信息
            char stdBuffer[1024];
            time_t timestamp = time(nullptr); // 获得时间戳
            struct tm* local_time = localtime(&timestamp); // 将时间戳转换为本地时间

            snprintf(stdBuffer, sizeof stdBuffer, "[%s][pid:%s][%d-%d-%d %d:%d:%d]",
                gLevelMap[level],
                std::to_string(getpid()).c_str(),
                local_time->tm_year + 1900, local_time->tm_mon + 1, local_time->tm_mday,
                local_time->tm_hour, local_time->tm_min, local_time->tm_sec
            );

            // 3.收集日志自定义部分信息
            char logBuffer[1024];
            va_list args; // 声明可变参数列表, 实际时一个 char* 类型
            va_start(args, format); // 初始化可变参数列表
            vsnprintf(logBuffer, sizeof logBuffer, format, args); // int vsnprintf(char *str, size_t size, const char *format, va_list ap); 是一个可变参数函数，将格式化后的字符串输出到缓冲区中。类似带 v 开头的可变参数函数有很多
            va_end(args); // 清理可变参数列表, 类似 close() 和 delete

            // 4.拼接为一个完整的消息
            std::string message;
            message += "--> 标准日志:"; message += stdBuffer;
            message += "\t 用户日志:"; message += logBuffer;
            message += "\n";

            // 5.打印日志消息
            _WriteLog(level, message);
        }

    private:
        bool _debugShow;
        WriteMode _writeMode;
        std::string _logFileName;
    };
}